
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef RT_VER_NUM
#define RT_VER_NUM 0x40100
#endif

#if RT_VER_NUM >= 0x40100
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#else
#include <dfs_fs.h>
#endif

#include "quicklz.h"

// #define malloc     rt_malloc
// #define free       rt_free
#define RT_NULL  NULL
#define rt_malloc malloc
#define rt_free free
#define rt_uint8_t u_int8_t 
#define rt_kprintf printf


#define BLOCK_HEADER_SIZE              4
#define COMPRESS_BUFFER_SIZE           4096
#define DCOMPRESS_BUFFER_SIZE          4096

/* Buffer padding for destination buffer, least size + 400 bytes large because incompressible data may increase in size. */
#define BUFFER_PADDING                 QLZ_BUFFER_PADDING

#if QLZ_STREAMING_BUFFER != 0
    #error Define QLZ_STREAMING_BUFFER to a zero value for this demo
#endif


static int quicklz_compress_file(int fd_in, int fd_out)
{
    /* Start to compress file  */
    qlz_state_compress *state_compress = RT_NULL;
    rt_uint8_t *cmprs_buffer = RT_NULL, *buffer = RT_NULL;
    rt_uint8_t buffer_hdr[BLOCK_HEADER_SIZE] = { 0 };
    size_t cmprs_size = 0, block_size = 0, totle_cmprs_size = 0;
    size_t file_size = 0, i = 0;
    int ret = 0;

    file_size = lseek(fd_in, 0, SEEK_END);
    lseek(fd_in, 0, SEEK_SET);

    cmprs_buffer = (rt_uint8_t *) malloc(COMPRESS_BUFFER_SIZE + BUFFER_PADDING);
    buffer = (rt_uint8_t *) malloc(COMPRESS_BUFFER_SIZE);
    if (!cmprs_buffer || !buffer)
    {
        rt_kprintf("[qlz] No memory for cmprs_buffer or buffer!\n");
        ret = -1;
        goto _exit;
    }

    state_compress = (qlz_state_compress *) malloc(sizeof(qlz_state_compress));
    if (!state_compress)
    {
        rt_kprintf("[qlz] No memory for state_compress struct, need %d byte, or you can change QLZ_HASH_VALUES to 1024 !\n",
                sizeof(qlz_state_compress));
        ret = -1;
        goto _exit;
    }
    memset(state_compress, 0x00, sizeof(qlz_state_compress));

    rt_kprintf("[qlz]compress start : ");
    for (i = 0; i < file_size; i += COMPRESS_BUFFER_SIZE)
    {
        if ((file_size - i) < COMPRESS_BUFFER_SIZE)
        {
            block_size = file_size - i;
        }
        else
        {
            block_size = COMPRESS_BUFFER_SIZE;
        }

        memset(buffer, 0x00, COMPRESS_BUFFER_SIZE);
        memset(cmprs_buffer, 0x00, COMPRESS_BUFFER_SIZE + BUFFER_PADDING);

        read(fd_in, buffer, block_size);

        /* The destination buffer must be at least size + 400 bytes large because incompressible data may increase in size. */
        cmprs_size = qlz_compress(buffer, (char *) cmprs_buffer, block_size, state_compress);

        /* Store compress block size to the block header (4 byte). */
        buffer_hdr[3] = cmprs_size % (1 << 8);
        buffer_hdr[2] = (cmprs_size % (1 << 16)) / (1 << 8);
        buffer_hdr[1] = (cmprs_size % (1 << 24)) / (1 << 16);
        buffer_hdr[0] = cmprs_size / (1 << 24);

        write(fd_out, buffer_hdr, BLOCK_HEADER_SIZE);
        write(fd_out, cmprs_buffer, cmprs_size);

        totle_cmprs_size += cmprs_size + BLOCK_HEADER_SIZE;
        rt_kprintf(">");
    }

    rt_kprintf("\n");
    rt_kprintf("[qlz]compressed %d bytes into %d bytes , compression ratio is %d%!\n", file_size, totle_cmprs_size,
            (totle_cmprs_size * 100) / file_size);
_exit:
    if (cmprs_buffer)
    {
        free(cmprs_buffer);
    }

    if (buffer)
    {
        free(buffer);
    }

    if (state_compress)
    {
        free(state_compress);
    }

    return ret;
}

static int quicklz_decompress_file(u_int8_t* i_buffer, u_int32_t i_length, u_int8_t* dcmprs_buffer, u_int32_t *o_len)
{
	/* Start to decompress file  */
    qlz_state_decompress *state_decompress = RT_NULL;
    rt_uint8_t buffer_hdr[BLOCK_HEADER_SIZE] = { 0 };
    size_t dcmprs_size = 0, block_size = 0, total_dcmprs_size = 0;

	u_int32_t i = 0;
    int ret = 0;

    if (!dcmprs_buffer || !i_buffer)
    {
        rt_kprintf("[qlz] No memory for dcmprs_buffer or buffer!\n");
        ret = -1;
        goto _dcmprs_exit;
    }

    state_decompress = (qlz_state_decompress *) malloc(sizeof(qlz_state_decompress));
    if (!state_decompress)
    {
        rt_kprintf("[qlz] No memory for state_decompress struct!\n");
        ret = -1;
        goto _dcmprs_exit;
    }
    memset(state_decompress, 0x00, sizeof(qlz_state_decompress));

    rt_kprintf("[qlz]decompress start : ");
    for (i = 0; i < i_length; i += BLOCK_HEADER_SIZE + block_size)
    {
        /* Get the decompress block size from the block header. */
		memcpy(buffer_hdr, i_buffer, BLOCK_HEADER_SIZE);
        block_size = buffer_hdr[0] * (1 << 24) + buffer_hdr[1] * (1 << 16) + buffer_hdr[2] * (1 << 8) + buffer_hdr[3];

        // memset(i_buffer, 0x00, COMPRESS_BUFFER_SIZE + BUFFER_PADDING);
        // memset(dcmprs_buffer, 0x00, DCOMPRESS_BUFFER_SIZE);
		// read(fd_in, i_buffer, block_size);

        dcmprs_size = qlz_decompress((const char *)(i_buffer+BLOCK_HEADER_SIZE), dcmprs_buffer, state_decompress);
        // write(fd_out, dcmprs_buffer, dcmprs_size);

        total_dcmprs_size += dcmprs_size;
        rt_kprintf(">");
    }
    rt_kprintf("\n");
    rt_kprintf("decompressed %d bytes into %d bytes !\n", i_length, total_dcmprs_size);
    *o_len = total_dcmprs_size;

_dcmprs_exit:
    if (state_decompress)
    {
        free(state_decompress);
    }

    return ret;
}


int main(int argc, char ** argv)
//int quicklz_test(int argc, char ** argv)
{
    int fd_in = -1 , fd_out = -1;
    int ret  = 0;
	size_t file_size = 0;

    if (argc != 4)
    {
        rt_kprintf("Usage:\n");
        rt_kprintf("qlz_test -c [file] [cmprs_file]          -compress \"file\" to \"cmprs_file\" \n");
        rt_kprintf("qlz_test -d [cmprs_file] [dcmprs_file]   -dcompress \"cmprs_file\" to \"dcmprs_file\" \n");
        
        ret = -1;
        goto _exit;
    }

    fd_in = open(argv[2], O_RDONLY, 0664);
    if (fd_in < 0)
    {
        rt_kprintf("[qlz] open the input file : %s error!\n", argv[2]);
        ret = -1;
        goto _exit;
    }

    fd_out = open(argv[3], O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (fd_out < 0)
    {
        rt_kprintf("[qlz] open the output file : %s error!\n", argv[3]);
        ret = -1;
        goto _exit;
    }

    if(memcmp("-c", argv[1], strlen(argv[1])) == 0)
    {

        if(quicklz_compress_file(fd_in, fd_out) < 0)
        {
            rt_kprintf("[qlz] quciklz compress file error!\n");
        }

    }
    else if(memcmp("-d", argv[1], strlen(argv[1])) == 0)
    {
		file_size = lseek(fd_in, 0, SEEK_END);
		lseek(fd_in, 0, SEEK_SET);
        
		if (file_size <= BLOCK_HEADER_SIZE)
		{
			rt_kprintf("[qlz] decomprssion file size : %d error!\n", file_size);
			ret = -1;
			goto _exit;
		}

        rt_uint8_t *dcmprs_buffer = RT_NULL, *file_buffer = RT_NULL;
        dcmprs_buffer = (rt_uint8_t *) malloc(DCOMPRESS_BUFFER_SIZE);
        file_buffer = (rt_uint8_t *) malloc(DCOMPRESS_BUFFER_SIZE + BUFFER_PADDING);
        u_int32_t dcmprs_len = 0;
		read(fd_in, file_buffer, file_size);
        if(quicklz_decompress_file(file_buffer, file_size, dcmprs_buffer, &dcmprs_len) < 0)
        {
            rt_kprintf("[qlz] quciklz decompress file error!\n");
            free(file_buffer);
            free(dcmprs_buffer);
            goto _exit;
        }
        write(fd_out, dcmprs_buffer, dcmprs_len);
        free(file_buffer);
        free(dcmprs_buffer);

    }
    else
    {
        rt_kprintf("Usage:\n");
        rt_kprintf("qlz_test -c [file] [cmprs_file]          -compress \"file\" to \"cmprs_file\" \n");
        rt_kprintf("qlz_test -d [cmprs_file] [dcmprs_file]   -dcompress \"cmprs_file\" to \"dcmprs_file\" \n");
        
        ret = -1;
        goto _exit;
    }

_exit:
    if(fd_in >= 0)
    {
        close(fd_in);
    }

    if(fd_out >= 0)
    {
        close(fd_out);
    }

    return ret;
}

